package com.hup.minicontroller.common.netty.handler;

import android.util.Log;

import com.hup.minicontroller.common.netty.NettyTCPClient;
import com.hup.minicontroller.common.netty.NettyTCPServer;
import com.hup.minicontroller.common.netty.annotation.DataHandlerMethod;
import com.hup.minicontroller.common.netty.model.BaseDataPacket;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;

import io.netty.channel.Channel;
import lombok.Getter;

/**
 * 实现类似Spring的Controller的功能,实现socket数据解码并转发到Controller对应的业务方法;<br>
 * 用法:
 * <pre>
 * .继承本接口,建议写成独立的Controller类,规则: {@link DataHandlerRegister#addDataHandler(DataHandler)}
 * .定义{@link BaseDataPacket}处理方法{@link DataHandlerMethod},规则: {@link DataMethodInvokerParser#parseMethod(Method, DataHandler)}
 * .使用{@link DataHandlerRegister#addDataHandler(DataHandler)} 把 {@link DataHandler}注册到 {@link NettyTCPServer},{@link NettyTCPClient}中
 *      .{@link NettyTCPServer},{@link NettyTCPClient}会在建立连接前内部调用 {@link DataHandlerRegister#registerDataHandlerMap()},实现解码功能
 * </pre>
 *
 * @author hugan
 * @date 2021/4/26
 */
public interface DataHandler {

    /**
     * 反射工具类,实现把{@link BaseDataPacket}转发到目标业务方法
     */
    class DataMethodInvoker {
        private static final String TAG = DataMethodInvoker.class.getSimpleName();

        @Getter
        private final int dataType;
        @Getter
        private final Class<? extends BaseDataPacket> dataClz;
        @Getter
        private final Method method;
        private final int paramLength;
        private final int dataIndex;
        private final Integer channelIndex;
        private final DataHandler dataHandlerInstance;

        private DataMethodInvoker(int dataType, Class<? extends BaseDataPacket> dataClz, Method method,
                                  int paramLength, int dataIndex, Integer channelIndex,
                                  DataHandler dataHandlerInstance) {
            this.dataType = dataType;
            this.dataClz = dataClz;
            this.method = method;
            this.paramLength = paramLength;
            this.dataIndex = dataIndex;
            this.channelIndex = channelIndex;
            this.dataHandlerInstance = dataHandlerInstance;
        }

        /**
         * 通过反射,传递数据并调用方法
         */
        public void invoke(BaseDataPacket data, Channel channel) {
            Object[] args = new Object[paramLength];
            args[dataIndex] = data;
            if (channelIndex != null) args[channelIndex] = channel;
            try {
                method.invoke(dataHandlerInstance, args);
            } catch (ReflectiveOperationException | IllegalArgumentException e) {
                Log.e(TAG, "invoke: 反射调用异常:{}" + method, e);
            }
        }

        @Override
        public String toString() {
            return "DataMethodInvoker{" +
                    "dataType=" + dataType +
                    ", method=" + method +
                    '}';
        }
    }

    class DataMethodInvokerParser {
        private static final String TAG = DataMethodInvokerParser.class.getSimpleName();

        private static final Map<Class<? extends BaseDataPacket>, DataMethodInvoker> EMPTY_METHOD_MAP = new HashMap<>();

        /**
         * 解析多个[数据处理类,及类方法],构造DataMethodInvokerMap
         */
        public static Map<Class<? extends BaseDataPacket>, DataMethodInvoker> parseHandlers(
                List<DataHandler> dataHandlers) {
            if (dataHandlers.isEmpty()) {
                return EMPTY_METHOD_MAP;
            }
            Map<Class<? extends BaseDataPacket>, DataMethodInvoker> res = new HashMap<>();
            for (DataHandler dataHandler : dataHandlers) {
                Map<Class<? extends BaseDataPacket>, DataMethodInvoker> map = DataMethodInvokerParser.parseHandler(dataHandler);
                for (Entry<Class<? extends BaseDataPacket>, DataMethodInvoker> entry : map.entrySet()) {
                    Class<? extends BaseDataPacket> clz = entry.getKey();
                    DataMethodInvoker invoker = entry.getValue();
                    DataMethodInvoker old = res.get(clz);
                    if (old != null && !Objects.equals(old.getMethod(), invoker.getMethod())) {
                        Log.w(TAG, "parseHandlers: [" + clz.getSimpleName() + "]的处理器被替换, new=" + invoker.getMethod() + ", old=" + old.getMethod());
                    }
                    res.put(clz, invoker);
                }
            }
            return res;
        }

        /**
         * 解析[数据处理类,及内方法]
         *
         * @return map[数据类型, DataMethodInvoker]
         */
        private static Map<Class<? extends BaseDataPacket>, DataMethodInvoker> parseHandler(DataHandler dataHandler) {
            HashMap<Class<? extends BaseDataPacket>, DataMethodInvoker> map = new HashMap<>();
            Method[] methods = dataHandler.getClass().getMethods();//所有public方法,包含父类的
            for (Method method : methods) {
                DataMethodInvoker invoker = DataMethodInvokerParser.parseMethod(method, dataHandler);
                if (invoker != null) {
                    Class<? extends BaseDataPacket> clz = invoker.getDataClz();
                    DataMethodInvoker old = map.get(clz);
                    if (old != null && !Objects.equals(old, invoker)) {
                        Log.w(TAG, "parseHandler: [" + clz.getSimpleName() + "]的处理器被替换, new=" + invoker.getMethod() + ", old=" + old.getMethod());
                    }
                    map.put(clz, invoker);
                }
            }
            return map;
        }

        /**
         * 判断传入方法是否为有效的 {@link DataHandlerMethod}, 定义异常时会有警告日志<pre>
         * 定义规则:
         * .以public和 {@link DataHandlerMethod}修饰的,能识别父类方法
         * .入参必须包含{@link BaseDataPacket}的实现类(非接口,非抽象类),有且仅有一个,入参位置不限
         * .入参允许包含 {@link Channel},无或仅有一个,入参位置不限
         * .入参不允许[基本类型],会在调用方法时出现反射异常
         * .允许其他对象入参,但由 {@link DataMethodInvoker#invoke(BaseDataPacket, Channel)} 通过反射调用时,此类入参是null
         * .其他问题: 重复定义某 {@link BaseDataPacket} 处理方法时,旧方法会被覆盖
         * </pre>
         *
         * @param method 被判断的方法
         * @return null-不是符合的方法;notNull-是符合的方法,返回触发器
         */
        @SuppressWarnings("unchecked")
        private static DataMethodInvoker parseMethod(Method method, DataHandler dataHandler) {
            DataHandlerMethod annotation = method.getAnnotation(DataHandlerMethod.class);
            if (annotation == null) return null;

            //下面都是DataHandlerMethod的,定义异常时,都会打印警告日志
            Class<?>[] parameterTypes = method.getParameterTypes();
            int paramLength = parameterTypes.length;
            //log.info("m={}, parameterTypes="+ method, paramLength);

            Integer dataType = null;
            Class<? extends BaseDataPacket> dataClz = null;
            Integer dataIndex = null;
            Integer channelIndex = null;
            for (int i = 0; i < paramLength; i++) {
                Class<?> type = parameterTypes[i];
                if (BaseDataPacket.class.isAssignableFrom(type)) {
                    //处理BaseDataPacket入参
                    int modifiers = type.getModifiers();
                    if (Modifier.isAbstract(modifiers) || Modifier.isInterface(modifiers)) {
                        Log.w(TAG, "parseMethod: BaseDataPacket不能为抽象类或接口:" + method);
                        return null;
                    }
                    if (dataIndex != null) {
                        Log.w(TAG, "parseMethod: BaseDataPacket入参重复:" + method);
                        return null;
                    }
                    try {
                        BaseDataPacket instance = (BaseDataPacket) type.newInstance();
                        dataType = instance.getType();
                        dataClz = (Class<? extends BaseDataPacket>) type;
                        dataIndex = i;
                    } catch (ReflectiveOperationException e) {
                        Log.w(TAG, "parseMethod: BaseDataPacket实例化异常:" + method);
                        return null;
                    }
                } else if (type.equals(Channel.class)) {
                    //处理Channel入参
                    if (channelIndex != null) {
                        Log.w(TAG, "parseMethod: Channel入参重复:" + method);
                        return null;
                    }
                    channelIndex = i;
                } else if (type.isPrimitive()) {
                    Log.w(TAG, "parseMethod: 不允许基本类型作为入参:" + method);
                    return null;
                }
            }
            if (dataType == null) {
                Log.w(TAG, "parseMethod: 不存在BaseDataPacket入参:" + method);
                return null;
            }
            return new DataMethodInvoker(dataType, dataClz, method, paramLength, dataIndex, channelIndex, dataHandler);
        }
    }

}
